﻿//===============================Image.cs==============================\\
//===Peter Bonnema======\\


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;

namespace Team2_Vision
{
    public class Image
    {
        private Boolean isLocked = false;
        private Bitmap source;
        private ImageLockMode lockMode;
        private BitmapData imageData;
        private byte[] imageDataArray;

        /// <summary>
        /// Allows you to get or set an array containing the image data. Store it in a local variable or field for a speed boost if you read multiple times.
        /// Throws an System.MemberAccessException if you try to set it while the image is locked and the input array is of a different size then the one already stored.
        /// </summary>
        public byte[] array
        {
            get { return imageDataArray; }
            set
            {
                if (isLocked & value.Length != imageDataArray.Length)
                    throw new System.MemberAccessException("The image is locked and the supplied array is of the wrong size (" + value.Length + "). It should be: " + imageDataArray.Length);
                imageDataArray = value;
            }
        }

        /// <summary>
        /// Allows you to get the BitmapData object associated with the locked image data.
        /// Throws a System.MemberAccessException if the image is not locked.
        /// </summary>
        public BitmapData ImageData
        {
            get
            {
                if (!isLocked)
                    throw new System.MemberAccessException("The image is not locked.");
                return imageData;
            }
        }

        /// <summary>
        /// Allows you to get the pixel size in bytes of the pixels of this image. Store it in a local variable or field for a speed boost if you read multiple times.
        /// Throws a System.MemberAccessException if the image is not locked.
        /// </summary>
        public byte PixelSize
        {
            get
            {
                if (!isLocked)
                    throw new System.MemberAccessException("The image is not locked.");
                return (byte)(Bitmap.GetPixelFormatSize(source.PixelFormat) / 8);
            }
        }

        /// <summary>
        /// Allows you to get the row lenght in bytes of every row of pixels of this image (PixelSize * ImageData.Width). Store it in a local variable or field for a speed boost if you read multiple times.
        /// Throws a System.MemberAccessException if the image is not locked.
        /// </summary>
        public int RowLength
        {
            get
            {
                if (!isLocked)
                    throw new System.MemberAccessException("The image is not locked.");
                return source.Width * PixelSize;
            }
        }

        /// <summary>
        /// Allows you to get the Bitmap object associated with this image object.
        /// Throws a System.MemberAccessException if the image is not locked.
        /// </summary>
        public Bitmap Source
        {
            get
            {
                if (!isLocked)
                    throw new System.MemberAccessException("The image is not locked.");
                return source;
            }
        }

        /// <summary>
        /// Returns a boolean indicating whether this image is locked.
        /// </summary>
        public Boolean IsLocked
        {
            get { return isLocked; }
        }

        /// <summary>
        /// Initializes an Image object by locking the image data from the supplied bitmap in memory using the specified lock mode.
        /// </summary>
        /// <param name="source">The bitmap to be locked in memory.</param>
        /// <param name="lockMode">The lockmode used with locking the image data in memory. The byte array returned by the array attribute will be empty if lockmode is equal to ImageLockMode.WriteOnly.</param>
        public Image(Bitmap source, ImageLockMode lockMode)
        {
            lockFromImage(source, lockMode);
        }

        /// <summary>
        /// Locks the image data of the supplied bitmap using the specified lock mode.
        /// Throws a System.InvalidOperationException if the image is already locked.
        /// </summary>
        /// <param name="sourceImage">The bitmap to be locked in memory.</param>
        /// <param name="lockMode">The lockmode used with locking the image data in memory. The byte array returned by the array attribute will be empty if lockMode is equal to ImageLockMode.WriteOnly.</param>
        public void lockFromImage(Bitmap sourceImage, ImageLockMode lockMode)
        {
            if (isLocked)
            {
                throw new InvalidOperationException("The image is already locked.");
            }

            source = sourceImage;
            this.lockMode = lockMode;
            imageData = source.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), lockMode, PixelFormat.Format24bppRgb);
            int imageSize = imageData.Stride * source.Height;
            imageDataArray = new byte[imageSize];

            if (lockMode != ImageLockMode.WriteOnly)
            {
                Marshal.Copy(imageData.Scan0, imageDataArray, 0, imageSize);
            }
            isLocked = true;
        }

        /// <summary>
        /// Unlocks the image data back into the original bitmap and returns it.
        /// Throws a System.InvalidOperationException if the image is not locked.
        /// </summary>
        /// <returns>The original bitmap specified by calling the constructor or the LockFromImage method.</returns>
        public Bitmap UnlockToBitmap()
        {
            if (!isLocked)
            {
                throw new InvalidOperationException("The image is not locked.");
            }

            if (lockMode != ImageLockMode.ReadOnly)
            {
                Marshal.Copy(imageDataArray, 0, imageData.Scan0, imageDataArray.Length);
            }
            source.UnlockBits(imageData);
            isLocked = false;
            imageData = null;
            imageDataArray = null;

            GC.Collect();

            return source;
        }

        /// <summary>
        /// This method converts the image to grey scale.
        /// Throws a System.InvalidOperationException if the image is not locked.
        /// </summary>
        public void ToGreyScale()
        {
            if (!isLocked)
            {
                throw new InvalidOperationException("The image is not locked.");
            }

            for (int i = 0; i < imageDataArray.Length; i += 3)
            {
                imageDataArray[i] = imageDataArray[i + 1] = imageDataArray[i + 2] =
                    (byte)(
                        (imageDataArray[i] + imageDataArray[i + 1] + imageDataArray[i + 2]) / 3
                    );
            }
        }

        /// <summary>
        /// Applies a treshold to the image so that every pixel lighter than the specified treshold will become 255. The rest of the pixels will be set to 0.
        /// </summary>
        /// <param name="treshold">The treshold to apply.</param>
        public void ApplyTreshold(byte treshold)
        {
            if (!isLocked)
            {
                throw new InvalidOperationException("The image is not locked.");
            }

            for (int i = 0; i < imageDataArray.Length; i += 3)
            {
                if (imageDataArray[i] > treshold)
                {
                    imageDataArray[i] = imageDataArray[i + 1] = imageDataArray[i + 2] = 255;
                }
                else
                {
                    imageDataArray[i] = imageDataArray[i + 1] = imageDataArray[i + 2] = 0;
                }
            }
        }

        /// <summary>
        /// First, the greyscale of every pixel will be calculated.
        /// Then the specified treshold will be applied so that every pixel lighter than the treshold will become 255. Otherwise, the pixel will be set to 0.
        /// </summary>
        /// <param name="treshold">The treshold to apply.</param>
        public void ToGreyScaleAndTreshold(byte treshold)
        {
            if (!isLocked)
            {
                throw new InvalidOperationException("The image is not locked.");
            }

            for (int i = 0; i < imageDataArray.Length; i += 3)
            {
                if ((imageDataArray[i] +
                    imageDataArray[i + 1] +
                    imageDataArray[i + 2]) / 3 > treshold)
                {
                    imageDataArray[i] = imageDataArray[i + 1] = imageDataArray[i + 2] = 255;
                }
                else
                {
                    imageDataArray[i] = imageDataArray[i + 1] = imageDataArray[i + 2] = 0;
                }
            }
        }

        /// <summary>
        /// Cuts the edges of your image to keep only the rectangle specified by the position (x,y) and the width and height and resizes the result to the specified width and height.
        /// Throws a System.InvalidOperationException if the image is not locked.
        /// </summary>
        /// <param name="x">The x position of the rectangle to keep.</param>
        /// <param name="y">The y position of the rectangle to keep.</param>
        /// <param name="width">The width of the rectangle to keep.</param>
        /// <param name="height">The height of the rectangle to keep.</param>
        /// <param name="newWidth">The new width of the image.</param>
        /// <param name="newHeight">The new height of the image.</param>
        public void CutAndResize(int x, int y, int width, int height, int newWidth, int newHeight)
        {
            source = UnlockToBitmap();

            Bitmap result = new Bitmap(newWidth, newHeight, PixelFormat.Format24bppRgb);
            Graphics g = Graphics.FromImage(result);
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
            g.DrawImage(source, new Rectangle(0, 0, newWidth, newHeight), new Rectangle(x, y, width, height), GraphicsUnit.Pixel);
            g.Dispose();

            lockFromImage(result, lockMode);
        }
    }
}
